package busuanzi.module;

import busuanzi.storage.MongoClient;
import busuanzi.util.ACache;
import busuanzi.util.Logs;
import busuanzi.util.UUIDs;
import com.alibaba.fastjson.JSON;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.bson.Document;

import java.net.URL;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static busuanzi.storage.MongoClient.newDocument;
import static busuanzi.storage.MongoClient.s;
import static busuanzi.util.Containers.newHashMap;
import static com.google.common.base.Strings.isNullOrEmpty;
import static io.netty.handler.codec.http.HttpHeaderNames.REFERER;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static sardine.Sardine.*;


/**
 * Auth: bruce-sha
 * Date: 2015/4/29
 */
public abstract class Services {


    public static void run() {

//        http://qifuguang.me/  参考 下方统计
        /*
         * 数据结构：
         * budsuanzi_site1:
         *                : _pv
         *                : _uv
         *                : page1
         */
        get("/counter", "application/vnd.busuanzi.v1.0+json", (request, response) -> {

            String callback = request.queryParams("jsonpCallback");
            if (isNullOrEmpty(callback)) halt(BAD_REQUEST);

            String refer = request.headers(REFERER);
            if (isNullOrEmpty(refer)) halt(BAD_REQUEST);

            Logs.log("busuanzi v1 from: {}", refer);

            final URL referURL = new URL(refer);

            String referHost = referURL.getHost();
            String referPath = referURL.getPath();

            response.header(HttpHeaderNames.CONTENT_TYPE, "application/json");

            String busuanziId = request.cookie("busuanziId");

            return MongoClient.bath("busuanzi/busuan", collection -> {

                final FindOneAndUpdateOptions options = new FindOneAndUpdateOptions()
                        .projection(newDocument("count", 1))
                        .upsert(true)
                        .maxTime(800, TimeUnit.MICROSECONDS)
                        .returnDocument(ReturnDocument.AFTER);

                Document siteUVDoc = collection.findOneAndUpdate(
                        newDocument("host", referHost, "path", "_uv"),
                        newDocument(
                                "$inc", newDocument("count", busuanziId == null ? 1 : 0),
                                "$currentDate", newDocument("lastModified", true)),
                        options);

                Document sitePVDoc = collection.findOneAndUpdate(
                        newDocument("host", referHost, "path", "_pv"),
                        newDocument(
                                "$inc", newDocument("count", 1),
                                "$currentDate", newDocument("lastModified", true)),
                        options);

                Document pagePVDoc = collection.findOneAndUpdate(
                        newDocument("host", referHost, "path", isNullOrEmpty(referURL.getPath()) ? "/" : referPath),
                        newDocument(
                                "$inc", newDocument("count", 1),
                                "$currentDate", newDocument("lastModified", true)),
                        options);

                Map values = newHashMap(
                        "site", sitePVDoc.getInteger("count", 1),
                        "page", pagePVDoc.getInteger("count", 1),
                        "site_uv", siteUVDoc.getInteger("count", 1),
                        "version", 1.1);

                return String.format("try{%s(%s);}catch(e){}", callback, JSON.toJSONString(values));
            });
        });

        /****************************** restful 2.0 ******************************/

        get("/busuanzi", "application/vnd.busuanzi.v2+json", (request, response) -> {

            Statistics.asynCount();

            String callback = request.queryParams("jsonpCallback");
            if (isNullOrEmpty(callback)) {
                Logs.error("busuanzi v2 null callback: {}", request.uri());
                halt(HttpResponseStatus.BAD_REQUEST);
            }

            String refer = request.headers(REFERER);
            if (isNullOrEmpty(refer)) {
                Logs.error("busuanzi v2 null refer: {}", request.headers());
                halt(HttpResponseStatus.BAD_REQUEST);
            }

            Logs.log("busuanzi v2 from: {}", refer);

            final URL referURL = new URL(refer);
            String referHost = referURL.getHost();
            String referPath = referURL.getPath();

            response.header(HttpHeaderNames.CONTENT_TYPE, "application/json");

            return MongoClient.bath("busuanzi/busuan", busuans -> {

                final String busuanziId = request.cookie("busuanziId");
                final FindOneAndUpdateOptions options = new FindOneAndUpdateOptions()
                        .projection(s("{count: 1}"))
                        .upsert(true)
                        .maxTime(1024, TimeUnit.MICROSECONDS)
                        .returnDocument(ReturnDocument.AFTER);//before会比after性能好吗

                int siteUV = busuans.findOneAndUpdate(
                        s("{host: '%s', path: '_uv'}", referHost),
                        s("{$inc: {count: %s}, $currentDate: {lastModified: true}}", busuanziId == null ? 1 : 0),
                        options).getInteger("count", 1);

                int sitePV = busuans.findOneAndUpdate(
                        s("{host: '%s', path: '_pv'}", referHost),
                        s("{$inc: {count: 1}, $currentDate: {lastModified: true}}"),
                        options).getInteger("count", 1);

                int pagePV = busuans.findOneAndUpdate(
                        s("{host: '%s', path: \"%s\"}", referHost, isNullOrEmpty(referPath) ? "/" : referPath),
                        s("{$inc: {count: 1}, $currentDate: {lastModified: true}}"),
                        options).getInteger("count", 1);

                Map values = newHashMap("version", 2.4,
                        "site_uv", Math.max(1, siteUV),
                        "site_pv", Math.max(1, sitePV),
                        "page_pv", Math.max(1, pagePV));

                return String.format("try{%s(%s);}catch(e){}", callback, JSON.toJSONString(values));
            });
        });

        /****************************** restful 3.0 ******************************/

        get("/busuanzi.v3", "application/vnd.busuanzi.v3+json", (request, response) -> {

            Statistics.asynCount();

            String callback = request.queryParams("jsonpCallback");
            if (isNullOrEmpty(callback)) halt(HttpResponseStatus.BAD_REQUEST);

            String refer = request.headers(REFERER);
            if (isNullOrEmpty(refer)) halt(HttpResponseStatus.BAD_REQUEST);

            Logs.log("busuanzi v3 from: {}", refer);

            response.header(HttpHeaderNames.CONTENT_TYPE, "application/json");

            // 还有进一步的优化思路：生产消费者queue中存储referURL，根据refer合并请求再更新数据库
            String json = ACache.get(refer, () -> {

                final URL referURL = new URL(refer);
                String referHost = referURL.getHost();
                String referPath = referURL.getPath();

                return MongoClient.bath("busuanzi/busuan", busuans -> {
                    final String busuanziId = request.cookie("busuanziId");
                    final FindOneAndUpdateOptions options = new FindOneAndUpdateOptions()
                            .projection(s("{count: 1}"))
                            .upsert(true)
                            .maxTime(800, TimeUnit.MICROSECONDS)
                            .returnDocument(ReturnDocument.BEFORE);//before会比after性能好吗

//                    List<WriteModel<Document>> writeModels = Lists.newArrayList();
//                    UpdateOneModel updateOneModel = new UpdateOneModel<Document>(
//                            s("{host: '%s', path: '_uv'}", referHost),
//                            s("{$inc: {count: %s}, $currentDate: {lastModified: true}}", busuanziId == null ? 1 : 0));
//                    writeModels.add(updateOneModel);
//                    BulkWriteOptions bulkWriteOptions = new BulkWriteOptions();
//                    BulkWriteResult bulkWriteResult = busuans.bulkWrite(writeModels, bulkWriteOptions);
//
//
//                    List<BulkWriteUpsert> bulkWriteUpserts = bulkWriteResult.getUpserts();
//                    bulkWriteUpserts.get(0).

                    int siteUV = busuans.findOneAndUpdate(
                            s("{host: '%s', path: '_uv'}", referHost),
                            s("{$inc: {count: %s}, $currentDate: {lastModified: true}}", busuanziId == null ? 1 : 0),
                            options).getInteger("count", 1);

                    int sitePV = busuans.findOneAndUpdate(
                            s("{host: '%s', path: '_pv'}", referHost),
                            s("{$inc: {count: 1}, $currentDate: {lastModified: true}}"),
                            options).getInteger("count", 1);

                    int pagePV = busuans.findOneAndUpdate(
                            s("{host: '%s', path: '%s'}", referHost, isNullOrEmpty(referURL.getPath()) ? "/" : referPath),
                            s("{$inc: {count: 1}, $currentDate: {lastModified: true}}"),
                            options).getInteger("count", 1);

                    final Map values = newHashMap(
                            "site_uv", Math.max(1, siteUV),
                            "site_pv", Math.max(1, sitePV),
                            "page_pv", Math.max(1, pagePV),
                            "version", 3.0);

                    // 统计信息


                    return JSON.toJSONString(values);
                });
            });

            return String.format("try{%s(%s);}catch(e){}", callback, json);
        });

        after("/*", (request, response) -> {
            String busuanziId = request.cookie("busuanziId");
            if (busuanziId != null) return;
            busuanziId = UUIDs.uuidUpperCase();
            response.cookie("busuanziId", busuanziId, Long.MIN_VALUE);
        });
    }
}
